Table of Contents

  1. Before all
  2. Types
  3. Conditionals
  4. Functions
  5. Anonymous functions
  6. Null Safety and Exceptions
  7. Collections
  8. Class definition
  9. Object initialization
  10. Inheritance
  11. Objects
  12. Generics
  13. Extensions
  14. Java Interoperability
  15. Corutines
  16. Functional Programming Basics
  17. References

1. Before all

What’s Kotlin

  1. By Jetbrains which created Intellij IDEA
  2. Targeting JVM and more, interoperatable with Java
  3. Concise and safe, with many modern features

the REPL(read, evaluate, print, loop)

Install Kotlin commandline tools in macOS:

1
brew install kotlin

Execute kotlinc to start REPL

2. Types

Built-in types

String, Char, Boolean, Int, Double, List<T>, Set<T>, Map<K, V>, Array<T>

Not like Java, Kotlin has no primitives (int, float etc.) hence avoids the boxing between primitives and objects.

However, in byte code generated by Kotlin, primitives are used to improve performance.

3. Conditionals

Conditional operators

Operator Description
< Evaluates whether the value on the left is less than the value on the right.
<= Evaluates whether the value on the left is less than or equal to the value on the right.
> Evaluates whether the value on the left is greater than the value on the right.
>= Evaluates whether the value on the left is greater than or equal to the value on the right.
== Evaluates whether the value on the left is equal to the value on the right (like equals in Java).
!= Evaluates whether the value on the left is not equal to the value on the right.
=== Evaluates whether the two instances point to the same reference (like == in Java).
!== Evaluates whether the two instances do not point to the same reference.

4. Functions

Kotlin supports file level functions

1
2
3
4
5
fun main() {
println(fileLevelFunc())
}

fun fileLevelFunc() = "file level" // expression function

Default value of parameters

1
2
3
4
5
6
7
fun main() {
greeting("Earth") // default value for 2nd parameter
}

fun greeting(name: String, greeting: String = "good morning") {
println("${greeting.capitalize()}! $name.") // e.g. Good morning! Saturn.
}

Named function parameters

1
2
3
4
5
6
7
fun main() {
greeting(greeting = "Hello", name = "Earth") // named parameters
}

fun greeting(name: String, greeting: String = "good morning") {
println("${greeting.capitalize()}! $name.") // e.g. Good morning! Saturn.
}

Function names in backticks

It’s said this is useful to call Java methods whose names are reserved words in Kotlin, or to name some special methods.

1
2
3
4
5
fun `😂`() { }

fun main() {
`😂`() // call a function
}

Inline functions

Kotlin support inline functions, which is good for performance.

1
2
3
4
5
6
7
inline fun factorial(n: Int): Int {
return if (n > 2) {
n * factorial(n - 1) // wrong, no recursion inside inline functions
} else {
n
}
}

5. Anonymous functions

The complete structure of an anoymous function should be like:

1
2
3
4
5
val funcVariable: (ParamType1, ParamType2) -> ReturnType = 
{ param1: ParamType1, param2: ParamType2 ->
...
expr_to_be_returned // no return keyword, only the returned expression
}

An example of anoymous function:

1
2
3
4
val numLetters = "Mississippi".count({ letter ->
letter == 's'
})
print(numLetters) // would Prints 4
1
2
3
4
5
6
7
8
/**
* Returns the number of characters matching the given [predicate].
*/
public inline fun CharSequence.count(predicate: (Char) -> Boolean): Int {
var count = 0
for (element in this) if (predicate(element)) ++count
return count
}

Parameter types are usually omitted when they caould be inferred. Single parameter’s name could be omitted if its type could ba inferred, and we take it as the parameter name. Unused parameter name could be marked as _.

Examples:

1
2
3
4
5
6
7
8
val numLetters = "Mississippi".count({
it == 's'
})

val afterFilter = "Mississippi".filterIndexed({ _, char->
char != 's'
})
print(afterFilter) // prints Miiippi

Note: an anonymous function must be called immediately, assigned to a variable, or be passed as a parameter.

Anonymous functions as parameter is often in a different style when it is required as the last parameter in another function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// count starting from a specified index.
fun count(str: String, startIdx: Int, predicate: (Char) -> Boolean): Int {
return str.substring(startIdx).count(predicate)
}

// style 1
count("Mississippi", 5, {
it == 's'
})

// style 2, looks like definition of count,
// but actually it's anonymous function as parameter
count("Mississippi", 5) {
it == 's'
}

Want to declare a function type that returns something like void in Java? You need the Unit.

You can also refer a non anonymous function as parameter in call to another function by using ::. But I don’t think this is common.

1
2
3
4
5
fun isS(char: Char): Boolean {
return char == 's'
}

val numLetters = "Mississippi".count(::isS)

6. Null Safety and Exceptions

For nullable variables, there are some common solutions to handle potential null pointer exceptions.

Safe call operator:

1
var beverage = readLine()?.capitalize() // the beverage would be String?.

Nonnull assertion:

1
var beverage = readLine()!!.capitalize() // only if you're really confident

Check before use:

1
2
3
4
5
6
val result = readLine()
var beverage = if (result != null) { // type of beverage would be String.
result.capitalize() + result.toLowerCase()
} else {
""
}

However, check before use isn’t always safe, e.g. a field of an object might be assigned null by another thread between your null checking and usage of the field. For such case, you would need a local variable to get rid of this risk:

1
2
3
4
5
6
val localVar = a.b
if (localVar != null) {
// do something refers localVar
} else {
...
}

Elvis operator:

1
var beverage = readLine()?.capitalize() ?: ""  // beverage would be String.

Exceptions in Kotlin

Kotlin doesn’t require handling of those checked Exceptions of Java. Therefore you can do IO operations without try-catch and exception declaration in the method signature.

7. Collections

destructuring

1
2
3
val (type, name, price) = aList  // fetch the leading 3 elements from a list.

val (type, _, price) = aList // skip elements in specified indexes.

protection of immutable list is not valid at runtime

1
2
3
val a = listOf(1, 2, 3)
(a as MutableList<Int>).set(0, 100)
println(a) // would print [100, 2, 3]

8. Class definition

Visibility modifiers of class members

Modifier Description
public (default) The function or property will be accessible by code outside of the class. By default, functions and properties without a visibility modifier are public.
private The function or property will be accessible only within the same class.
protected The function or property will be accessible only within the same class or its subclass.
internal The function or property will be accessible within the same module.

Class fields

For class fields, default getter is available for val and var fields. But setter is only for var fields. Yet we can override the default setter and getter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class X(_name: String, _age: Int) {

// a field always return capitalized string when you get it
val name = _name
get() = field.capitalize()

// a field only allows increasing
var age = _age
set(value) {
if (value > field) {
field = value
}
}
}

You can override the visibility of a setter to make it less permissive then field’s visibility. E.g., you want to make a field readable everywhere but only writable in the file:

1
2
3
4
5
6
7
8
9
class X(_age: Int) {
// a field only allows increasing in current file.
var age = _age
private set(value) {
if (value > field) {
field = value
}
}
}

Computed fields

Has getter or/and setter but no real fields to store values.

1
2
3
4
class Dice() {
val rolledValue
get() = (1..6).shuffled().first()
}

9. Object initialization

The primary contructor

Add parameter list inside a pair of parenthesis after the class name, make it looks like a method. For fields using default getter and setter, you can declare them inside the parameter list by adding var or val:

1
2
// visibility modifier and default values are also available
class X(val name: String, private var age: Int = 0)

secondary constructor

Must call the primary constructor or another secondary constructor(of course no cycle call)

1
2
3
4
class X(val name: String, private var age: Int = 0) {
// call primary constructor with default parameter
constructor(): this("Unknown")
}

Apart from default parameter values, named parameters are also available for constructors.

Lazy initialization

Only initialize a field when it’s accessed (by the getter)

1
2
3
4
5
class X {
val number: Int by lazy {
(0 until 10).shuffled().first() // not good example
}
}

10. Inheritance

In Java, we only override methods. But in Kotlin, both class functions and fields could be overridden. Also, in Kotlin, classes, functions and fields must be marked open explicity to make them inheritable or able to be overridden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
open class Room(val name: String) {
protected open val dangerLevel = 5

fun description() = "Room: $name\n" +
"Danger level: $dangerLevel"

open fun load() = "Nothing much to see here..."
}

class TownSquare : Room("Town Square") {
override val dangerLevel = super.dangerLevel - 3

final override fun load() = "The villagers cheer as you enter!"
}

Functions are final by default unless they are inherited from an open class. To prevent inherited functions from being overriden again, we can mark it final.

In Kotlin’s inheritance, we always have:

However, when compiled into bytecode, the class Room doesn’t inherits the class Any in JVM rule.

11. Objects

singleton by object keyword

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
UserManager.getUser(2)
}

// constructors are not allowed for such objects
object UserManager {
private val users = mutableListOf<String>("A", "B")

fun getUser(idx: Int): String? {
return users.getOrNull(idx)
}
}

Anonymous inner class

In java, we often implement interfaces or extend simple classes as anonymous inner class, such as click listeners and view adapters.

In Kotlin, they could be divided into 2 categories. For interfaces with only one method and declared in Java, we can use a format called Lambda:

1
2
3
4
5
6
7
8
9
10
11
generateButton.setOnClickListener { view ->
fetchData()
}

fun useI(i: I) {

}

useI(I {
// ...
})
1
2
3
4
// I.java
public interface I {
void f();
}

For other cases, the format is like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
listView.adapter = object : BaseAdapter() {  // call the constructor here.
override fun getView(pos: Int, view: View?, parent: ViewGroup?): View {
// ...
}

override fun getItem(pos: Int): Any {
// ...
}

override fun getItemId(pos: Int): Long {
// ...
}

override fun getCount(): Int {
// ...
}
}

Nested classes by default cannot access members of the outer class, unless they are marked inner:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Outer {
fun outerFunc() = "1"

class Nested {
init {
outerFunc() // wrong, unable to access
}
}

inner class Inner {
init {
outerFunc() // ok
}
}
}

object UserManager {
private val users = mutableListOf<String>("A", "B")

class User {
init {
users // ok. this is nested class inside an object class.
}
}
}

Data classes

Data classes are suitable for model, that’s what the data means.

1
2
3
data class Coordinate(var x: Int, var y: Int) {
val inFirstQuadrant = (x >0 && y > 0)
}

when use the data classes:

  • Implementation of toString, equal and copy regarding the fields declared in the primary constructor;

  • Must have a primary constructor whose all parameters are marked with val or var;

  • Cannot be abstract, open, or inner.

Operator overloading

1
2
3
4
5
6
7
8
9
data class Coordinate(var x: Int, var y: Int) {
operator fun plusAssign(another: Coordinate) {
x += another.x
y += another.y
}
}

val coo = Coordinate(1, 2)
coo += Coordinate(2, 3)
1
2
3
4
5
6
7
8
9
10
object Fact {
operator fun invoke(n: Int): Int {
return if (n > 2)
n * this(n-1)
else
n
}
}

println(Fact(5))

12 Generics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open class Book(val name: String)

class Novel(name: String): Book(name)

class Textbook(name: String): Book(name)

class Bookshelf<T: Book>(vararg val books: T)

val shelf1 = Bookshelf(Novel("A song of Ice & Fire"),
Novel("The load of the rings"))
val shelf2 = Bookshelf(Novel("A song of Ice & Fire"),
Textbook("Introduction to Algorithms"))

fun <T: CharSequence> count(cs: T, startIdx: Int, char: Char) =
cs.substring(startIdx).count {
it == char
}
1
2
3
val list1: List<Book> = listOf<Novel>(Novel("A"), Novel("B"))
// the following line is wrong!
val list2: MutableList<Book> = mutableListOf<Novel>(Novel("A"), Novel("B"))

Actually the List interface is like this:

1
2
3
public interface List<out E> : Collection<E> {
...
}

Here out means: any fields of a List whih the generic type E must be val. Thus E is a type only for readable fields.

On the other hand, there is in, which means a generic type is only for writable variables:

1
2
3
4
5
6
7
8
9
class A<in T> { // no fields of type T can be available to outside
private var item: T? = null

fun setItem(newItem: T) {
item = newItem
}
}

val a: A<Novel> = A<Book>()

The wildcard in Java generics has some similarities with the in and out in Kotlin, but they are not equivalent concepts.

13. Extensions

Based on anonymous function and generics, Kotlin brings the extensions, which enable us to extend classes with new functions and fields without inheritance. The Kotlin standard library contains a large numbers of extensions.

The code below

1
2
3
4
5
6
fun count(str: String, startIdx: Int, predicate: (Char) -> Boolean) =
str.substring(startIdx).count(predicate)

count("Mississippi", 5) {
it == 's'
}

could be refactored as

1
2
3
4
5
6
7
// just like we have extended the String class, a final class.
fun String.count(startIdx: Int, predicate: (Char) -> Boolean) =
substring(startIdx).count(predicate)

"Mississippi".count(5) {
it == 's'
}

Extend with fields is also available:

1
2
3
4
val String.countS
get() = count { it == 's' || it == 'S' }

println("Mississippi".countS) // would print 4

Like computed properties, extended fields have no backing fields, actually the extended fields has no essential differences with extended methods.

Visibility modifiers are also valid for the extensions.

Extensions on nullable types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun <T> T?.chkNull(block1: (T) -> Unit, block2: () -> Unit) {
if (this != null) { // for such extension, the this is nullable
block1(this)
} else {
block2()
}
}

class C(val x: String? = listOf<String?>("s", null).shuffled().first())

val c = C()
c.x.chkNull({ // no need to write c.x?. even that c.x is nullable.
// it ...
}, {

})

Generics in extensions:

1
2
3
4
5
fun <T> T.prt() {
println(this)
}

(1 + 1).prt()

Extensions in Kotlin standard library:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Calls the specified function [block] with `this` value as its argument
* and returns its result.
*
* For detailed usage information see the documentation for [scope functions]
* (https://kotlinlang.org/docs/reference/scope-functions.html#let).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}

Usage of the let:

1
2
3
4
5
6
7
8
9
10
11
12
class Y(val pair: Pair<String, String>?)

val y = Y(Pair("hello", "world"))

y.pair?.let {
println(it.first + " " + it.second)
}

val p = y.pair
if (p != null) {
println(p.first + " " + p.second)
}

Extension function type:

1
2
3
4
5
val greetings: String.() -> Unit = {
println("Hello " + this.capitalize())
}

"Mississippi".greetings()

Another extension in Kotlin standard library:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Calls the specified function [block] with `this` value as its receiver
* and returns its result.
*
* For detailed usage information see the documentation for [scope functions]
* (https://kotlinlang.org/docs/reference/scope-functions.html#run).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}

File("x.txt").run {
setReadable(true)
setWritable(true)
setExecutable(false)
}

14. Java Interoperability

Nullity

1
2
3
4
5
6
7
8
9
10
11
// in java side, mark non-private fields, return type and parameter type
// with annotations, thus helps Kotlin side handle nullity
class JavaClass {
@NotNull
public String x = "x";

@Nullable
public String f(@NotNull String y) {
return null;
}
}

Jetbrains created their annotations like this:

While the annotations by Google is like this:

File level Kotlin members

Let’s say we have a kotlin file Hello.kt which is:

1
2
3
4
5
6
7
// in Hello.kt file
// by default, the Hello.kt would generate a JVM class as HelloKt,
// but we can change it like the following line.
@file:JvmName("Hello")

// file level members would be static members in generated JVM class
fun f() = "hello from kotlin."

Kotlin functions with default parameter values

1
2
3
4
5
// add this annotation to make it generates several overloaded JVM methods
@JvmOverloads
fun f(name: String = "Unknown", age: Int = 0) {

}

Access members of companion object in Java

1
2
3
4
5
6
7
8
9
class H {
companion object {
@JvmField // then H.x is ok in Java, or we need H.companion.x
val x = 10

@JvmStatic // then H.f is ok in Java
fun f(){ }
}
}

Exceptions

As we said earlier, Kotlin doesn’t require we to try-catch code blocks which may throw checked Java Exceptions or add a throws declaration in the method signatures. So if a Java method is called with throws declaration is called in kotlin, we might missed the exceptions warning by the Java method.

For Kotlin methods which would be called by Java and might throw checked exceptions, marked it with annotation:

1
2
3
4
@Throws(IOException::class)
fun f() {
throw FileNotFoundException()
}

Referred functions of Kotlin

1
2
3
4
// in Test.kt file
val translator = { utterance: String ->
println(utterance.toLowerCase().capitalize())
}
1
2
3
4
5
6
public class JavaClass {
public static void main(String[] args) {
Function1<String, Unit> translator = RoomKt.getTranslator();
translator.invoke("MONey");
}
}

What’s the Function1:

1
2
3
4
5
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}

15. Corutines

what are coroutines

  • coroutines are more lightweight than threads

  • a thread can schedule between multiple coroutines

  • a coroutine can switch between threads

async & launch

the most common ways to create coroutines

1
2
3
4
5
6
7
8
9
10
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job

public interface Job : CoroutineContext.Element {
public suspend fun join()
...
}
1
2
3
4
5
6
7
8
9
10
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>

public interface Deferred<out T> : Job {
public suspend fun await(): T
...
}

Deferred<T> is also a job, only that we can call its await to fetch an result returned by that coroutine. e.g:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private fun fetchData() {
GlobalScope.launch(Dispatchers.Main) {
// type of result: Deferred<CharacterData>
val result = GlobalScope.async(Dispatchers.Default) {
val (race, name, dex, wis, str) = URL(
"https://chargen-api.herokuapp.com/").readText().split(",")
CharacterData(race, name, dex, wis, str)
}
characterData = result.await()
displayCharacterData() // use the fetched characterData
}
}

private fun displayCharacterData() {
characterData.run {
nameTextView.text = name
raceTextView.text = race
dexterityTextView.text = dex
wisdomTextView.text = wis
strengthTextView.text = str
}
}

suspend functions

In my oppnion, a function with suspend means 3 things:

  1. it might takes a not short period of time to finish it (but not blocking a thread);
  2. if declared in kotlinx.coroutines, then it’s a cancelable point;
  3. might be a schedule point for threads.

The most common suspend method, delay, fulfills the 3 conclusions, so does the Deferred.await method.

the cancelable point

Able to cancel:

1
2
3
4
5
6
7
8
fun main() = runBlocking {
val job = launch {
delay(3000)
println("You won't see me since I would be canceled.")
}
delay(100)
job.cancel()
}

Unable to cancel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() = runBlocking {
val job = launch {
compute()
println("No way to cancel me!")
}
delay(100)
job.cancel()
}

fun compute() {
val startTime = System.currentTimeMillis()
var lastTime = startTime
while (lastTime - startTime < 3_000) {
if (System.currentTimeMillis() - lastTime >= 1000) {
lastTime += 1000
println((lastTime - startTime) / 1000)
}
}
}

Note: runBlocking would be explained later.

Coroutine context

1
2
3
4
5
fun main() = runBlocking<Unit> {
launch(Dispatchers.Default + CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
}

Coroutine scope

scope is just like variables have scope in most programming languages

1
2
3
4
5
fun func() {
if (1 + 1 == 2) {
val x = 3 // x belongs to the if scope
}
}

the CoroutineScope interface

1
2
3
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}

In kotlin, CoroutineScope is implemented by AbstractCoroutine, hence a coroutine means a CoroutineScope, but not vice versa.

Parent coroutine and child coroutine:

1
2
3
4
5
6
GlobalScope.launch { 
// only inside a coroutine can we call launch & async directly.
launch {

}
}

Direct calls of launch & async also means children coroutines inherits Dispatchers from parent unless override by providing a Dispatchers parameter.

1
2
3
4
5
6
GlobalScope.launch(Dispatchers.Main) { 
// only inside a coroutine can we call launch & async directly.
launch(Dispatchers.Default) {

}
}

Normally, parent finish after all children finish. But there is a extension method of CoroutineScope naming cancel. When it’s called, the whole coroutine tree are canceled.

1
2
3
4
5
6
7
8
/**
* Cancels this scope, including its job and all its children with an
* optional cancellation [cause].
* A cause can be used to specify an error message or to provide other
* details on a cancellation reason for debugging purposes.
* Throws [IllegalStateException] if the scope does not have a job in it.
*/
public fun CoroutineScope.cancel(cause: CancellationException? = null)

In kotlin, we have a GlobalScope which is an object singleton. Coroutines directly created from GlobalScope can last long as long as the JVM is on. GlobalScope is convenient for demo, however we may want coroutines to be canceled when an Activity is destroyed, for example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyActivity : AppCompatActivity() {
private val mainScope = MainScope()

override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}

mainScope.launch(Dispatchers.Main) {
// type of result: Deferred<CharacterData>
val result = mainScope.async(Dispatchers.Default) {
val (race, name, dex, wis, str) = URL(
"https://chargen-api.herokuapp.com/").readText().split(",")
CharacterData(race, name, dex, wis, str)
}
characterData = result.await()
displayCharacterData() // use the fetched characterData
}
}

Now let’s go back to explain runBlocking. runBlocking also starts a coroutine from GlobeSope, only that runBlocking coroutines prevent JVM from ending.

the coroutineScope(block) method

1
suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R

We can create a new scope with this coroutineScope method. The most common usage would be like this:

1
2
3
4
5
6
7
8
9
10
11
12
suspend fun doSomeThing(): Int = coroutineScope {
val deferred = async(Dispatchers.IO) {
delay(500)
3
}
deferred.await()
}

GlobalScope.launch(Dispatchers.Main) {
val result = doSomeThing()
// use the result
}

In the above code, we created a scope with coroutineScope. When doSomeThing is called from another coroutine, the scope we created would inherit context from the outer coroutine scope. More importantly, when the outer scope is cancelled, this scope would also be canceled.

In next section, we would see the coroutineScope has important role in exception handling.

Exception in coroutines

First, what is a crash in Android app? (excluding ANR).

The simplest and best way to handle exceptions, is catching exceptions locally, i.e. don’t let exceptions be passed between coroutines. Like the following:

1
2
3
4
5
6
7
8
9
10
11
12
suspend fun doSomeThing(): Int = coroutineScope {
val deferred: Deferred<Int> = async(Dispatchers.IO) {
try {
// ... the regular code block.
3 // assume 3 is a valid return
} catch (e: IndexOutOfBoundsException) {
// do something to handle
0 // assume 0 is a default return
}
}
deferred.await()
}

It’s simple, but why best? Because when an exception is not caught inside a coroutine, the exception would mostly cancel the whole coroutine tree.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
// all coroutines would be canceled, and the thread would crash
GlobalScope.launch {
launch {
delay(200)
println("You won't see me since I would be canceled.")
}
launch {
throw IndexOutOfBoundsException()
}

delay(200)
println("You won't see me since I would be canceled.")
}

runBlocking { delay(500) }
}

However, with the coroutineScope method, we can do more.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
suspend fun doSomeThing(): Int = coroutineScope {
val deferred: Deferred<Int> = async(Dispatchers.IO) {
throw IndexOutOfBoundsException()
}
launch { // this would be canceled
delay(200)
println("You won't see me since I would be canceled.")
}
deferred.await()
}

fun main() {
GlobalScope.launch {
launch {
delay(200)
println("I wouldn't be canceled.")
}
launch {
try { doSomeThing() } // doSomeThing throws the exception
catch (e: IndexOutOfBoundsException) {
println("caught the exception: $e")
}
}

delay(200)
println("I wouldn't be canceled, either.")
}

runBlocking { delay(500) }
}

the CancellationException

It is thrown when a coroutine is canceled, but won’t crash the JVM thread.

just like nothing happen:

1
2
3
4
5
6
7
fun main() {
// just like nothing happen
GlobalScope.launch {
throw CancellationException()
}
runBlocking { delay(500) }
}

JVM thread would crash:

1
2
3
4
5
6
fun main() {
GlobalScope.launch {
throw IndexOutOfBoundsException()
}
runBlocking { delay(500) }
}

CancellationException seems useless, but it can be caught if you want. Just surround the cancel point inside a coroutine with try-catch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
// just like nothing happen
GlobalScope.launch {
launch {
delay(100)
throw IndexOutOfBoundsException()
}
try { delay(50) } catch (e: CancellationException) {
println("caught 1: $e")
throw e // usually you should throw it again
}
try { delay(60) } catch (e: CancellationException) {
println("caught 2: $e")
throw e // usually you should throw it again
}
// ...
}
runBlocking { delay(500) }
}

Look the the code above, if you don’t throw again the CancellationException again, the coroutine would continue executing, which usually is abnormal.

the CoroutineExceptionHandler

We can set an CoroutineExceptionHandler to the root scope or the root coroutine to let the handler handle exceptions throws inside all coroutines.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val ceh = CoroutineExceptionHandler { _, e ->
println("Handled crash: $e")
}

//CoroutineScope(Dispatchers.IO).launch(ceh) { // this also works
CoroutineScope(Dispatchers.IO + ceh).launch {
launch(Dispatchers.Default) {
throw IndexOutOfBoundsException()
}
}

runBlocking { delay(500) }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// would crash!!
fun main() {
val ceh = CoroutineExceptionHandler { _, e ->
println("Handled crash: $e")
}

CoroutineScope(Dispatchers.IO).launch {
launch(Dispatchers.Default + ceh) {
throw IndexOutOfBoundsException()
}
}

runBlocking { delay(500) }
}

If a coroutine is created from async, the await function would also throw the exception escaped from the coroutine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
val ceh = CoroutineExceptionHandler { _, e ->
println("Handled crash: $e")
}

CoroutineScope(Dispatchers.IO).launch(ceh) {
val d: Deferred<Int> = async() { oops(); 3 }

try {
println(d.await())
} catch (e: IndexOutOfBoundsException) {
println("caught: $e")
}
}

runBlocking { delay(500) }
}

fun oops() {
throw IndexOutOfBoundsException()
}

16. Functional Programming Basics

Transforms

A transform function works on the contents of a collection by walking through the collection and transforming each item with a transformer function provided as an argument.

the map method

1
2
3
4
5
6
7
8
9
/**
* Returns a list containing the results of applying the given [transform]
* function to each element in the original collection.
*
* @sample samples.collections.Collections.Transformations.map
*/
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

Filters

A filter function accepts a predicate function that checks each element in a collection against a condition and returns either true or false.

the filter method

1
2
3
4
5
6
/**
* Returns a list containing only elements matching the given [predicate].
*/
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}

Combines

Combining functions take different collections and merge them into a new one.

the zip method

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Returns a sequence of values built from the elements of `this` sequence
* and the [other] sequence with the same index.
* The resulting sequence ends as soon as the shortest input sequence ends.
*
* The operation is _intermediate_ and _stateless_.
*
* @sample samples.collections.Sequences.Transformations.zip
*/
public infix fun <T, R> Sequence<T>.zip(other: Sequence<R>):
Sequence<Pair<T, R>> {
return MergingSequence(this, other) { t1, t2 -> t1 to t2 }
}

Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
// get primes
(2..1000).filter { num->
(2 until num).filter {
num % it == 0
}.isEmpty()
}

// get primes with the none
val primes = (2..1000).filter { num->
(2 until num).none {
num % it == 0
}
}

References

  1. Kotlin Programming: The Big Nerd Ranch Guide, awailable in the O’Reilly Learning platform.
  2. Official Kotlin coroutine guide.
  3. Exceptional Exceptions for Coroutines made easy…?